﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Brainee.Search;

namespace Brainee.Examples.Search
{
  /// <summary>
  /// The vacuum-cleaner problem is the first toy problem presented in AIMA (2nd ed. p64).
  /// 
  /// This can be formulated as a problem as follows:
  ///   - States: The agent is in one of two locations, each of which might or might not contain
  ///     dirt. Thus there are 2*2^2 = 8 possible world states.
  ///   - Initial state: Any state can be designated as the initial state.
  ///   - Successor function: This generates the legal states that result from trying the three 
  ///     actions (Left, Right, and Suck).
  ///   - Goal test: This checks whether all the squares are clean.
  ///   - Path cost: Each step costs 1, so the path cost is the number of steps in the path.
  ///
  /// A larger environment with n locations has n*2^n states.
  /// </summary>
  public class VacuumCleaner : IProblem
  {
#region States

  public class State : IState
  {
    public enum Location
    {
      Left,
      Right,
    }

    public enum Status
    {
      Clean,
      Dirty
    }

    public Status Left { get; private set; }
    public Status Right { get; private set; }
    public Location Position { get; private set; }

    public State (Status left, Status right, Location position)
    {
      Left = left;
      Right = right;
      Position = position;
    }

    public static State Random
    {
      get
      {
        Random random = new Random ((int)DateTime.Now.Ticks);
        return new State((Status)random.Next (2), (Status)random.Next (2), (Location)random.Next (2));
      }
    }
  }

  private class StateEqualityComparer : IEqualityComparer<IState>
  {
    public bool Equals (IState x, IState y)
    {
      return ((State)x).Left.Equals (((State)y).Left)
          && ((State)x).Right.Equals (((State)y).Right)
          && ((State)x).Position.Equals (((State)y).Position);
    }

    public int GetHashCode (IState obj)
    {
      return (int)((State)obj).Left
           ^ (int)((State)obj).Right
           ^ (int)((State)obj).Position;
    }
  }

#endregion
#region Actions

  public class Actions : IAction
  {
    private class SuckAction : Actions { }
    public static readonly IAction Suck = new SuckAction ();

    private class GoLeftAction : Actions { }
    public static readonly IAction GoLeft = new GoLeftAction ();

    private class GoRightAction : Actions { }
    public static readonly IAction GoRight = new GoRightAction ();
  }

#endregion
#region Constructor

    public VacuumCleaner ()
    {
      InitialState = State.Random;
    }

#endregion
#region IProblem implementation

    public IState InitialState { get; set; }

    public IEqualityComparer<IState> StateComparer
    {
      get { return new StateEqualityComparer (); }
    }

    public IEnumerable<ActionState> Successors (IState state)
    {
      List<ActionState> successors = new List<ActionState> ();

      State s = state as State;
      if (s == null)
        throw new ArgumentException ();

      if (s.Position == State.Location.Left)
      {
        successors.Add (new ActionState( Actions.Suck, new State (State.Status.Clean, s.Right, s.Position)));
      }
      else
      {
        successors.Add (new ActionState( Actions.Suck, new State (s.Left, State.Status.Clean, s.Position)));
      }
      successors.Add (new ActionState( Actions.GoLeft, new State (s.Left, s.Right, State.Location.Left)));
      successors.Add (new ActionState( Actions.GoRight, new State (s.Left, s.Right, State.Location.Right)));

      return successors;
    }

    public bool GoalTest (IState state)
    {
      State s = state as State;
      if (s == null)
        throw new ArgumentException ();

      return (s.Left == State.Status.Clean) && (s.Right == State.Status.Clean);
    }

    public double EstimatedCostToGoal (IState state)
    {
      State s = state as State;
      if (s == null)
        throw new ArgumentException ();

      double d = 0.0;
      if (s.Left == State.Status.Dirty)
        d += 1.0;
      if (s.Right == State.Status.Dirty)
        d += 1.0;

      return d;
    }

    public double StepCost (IState from, IAction action, IState to)
    {
      return 1.0;
    }

#endregion
  }
}
